package com.example.demo.generator.scaffold.service;

import com.example.demo.generator.codegen.SpringBootCodeGeneratorAdapter;
import com.example.demo.generator.codegen.exception.SqlParseException;
import com.example.demo.generator.codegen.model.ClassInfo;
import com.example.demo.generator.codegen.model.ParamInfo;
import com.example.demo.generator.scaffold.dto.GeneratedFileVO;
import com.example.demo.generator.scaffold.dto.ScaffoldGenerationRequest;
import com.example.demo.generator.scaffold.model.GeneratedFile;
import com.example.demo.generator.scaffold.model.ScaffoldOptions;
import com.example.demo.generator.scaffold.model.TemplateContext;
import com.example.demo.generator.scaffold.registry.BackendMybatisPlusTemplates;
import com.example.demo.generator.scaffold.registry.BlockchainTemplates;
import com.example.demo.generator.scaffold.registry.FiscoBaseTemplates;
import com.example.demo.generator.scaffold.registry.FrontendReactTemplates;
import com.example.demo.generator.scaffold.registry.FrontendVueTemplates;
import com.example.demo.generator.scaffold.registry.ProjectBootstrapTemplates;
import com.example.demo.generator.scaffold.registry.SolidityTemplates;
import org.springframework.stereotype.Service;

import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 脚手架代码生成入口，当前支持 MyBatis-Plus 基础 CRUD。
 */
@Service
@Slf4j
public class ScaffoldGenerationService {

    private final SpringBootCodeGeneratorAdapter codeGeneratorAdapter;
    private final TemplateRenderService templateRenderService;

    public ScaffoldGenerationService(SpringBootCodeGeneratorAdapter codeGeneratorAdapter,
                                     TemplateRenderService templateRenderService) {
        this.codeGeneratorAdapter = codeGeneratorAdapter;
        this.templateRenderService = templateRenderService;
    }

    /**
     * 解析 SQL 并生成 MyBatis-Plus CRUD 代码集合。
     */
    public List<GeneratedFile> generateMybatisPlusCrud(ScaffoldGenerationRequest request) throws SqlParseException {
        Objects.requireNonNull(request, "request must not be null");
        Objects.requireNonNull(request.getBasePackage(), "basePackage must not be null");
        Objects.requireNonNull(request.getModuleName(), "moduleName must not be null");
        Objects.requireNonNull(request.getCreateTableSql(), "createTableSql must not be null");

        ParamInfo paramInfo = new ParamInfo();
        paramInfo.setTableSql(request.getCreateTableSql());
        paramInfo.setOptions(request.getSqlParseOptions().toParamMap());

        ClassInfo classInfo = codeGeneratorAdapter.parseCreateTable(paramInfo);

        ScaffoldOptions scaffoldOptions = enrichOptions(classInfo, request.getScaffoldOptions());

        log.debug("Scaffold moduleBasePath={} options enableBlockchain={} chainFields={} databaseType={} author={}",
                request.getModuleBasePath(),
                scaffoldOptions.isEnableBlockchain(),
                scaffoldOptions.getChainFields().size(),
                scaffoldOptions.getDatabaseType(),
                scaffoldOptions.getAuthor());

        TemplateContext context = TemplateContext.builder()
                .basePackage(request.getBasePackage())
                .moduleName(request.getModuleName())
                .moduleBasePath(request.getModuleBasePath())
                .classInfo(classInfo)
                .options(scaffoldOptions)
                .rawCreateTableSql(request.getCreateTableSql())
                .frontendFramework(scaffoldOptions.getFrontendFramework())
                .build();

        List<GeneratedFile> generatedFiles = new ArrayList<>();
        ProjectBootstrapTemplates.all().forEach(descriptor -> {
            GeneratedFile file = templateRenderService.render(descriptor, context);
            generatedFiles.add(file);
        });
        BackendMybatisPlusTemplates.all().forEach(descriptor -> {
            GeneratedFile file = templateRenderService.render(descriptor, context);
            generatedFiles.add(file);
        });
        if (context.getFrontendFramework() == ScaffoldOptions.FrontendFramework.VUE) {
            FrontendVueTemplates.all().forEach(descriptor -> {
                GeneratedFile file = templateRenderService.render(descriptor, context);
                generatedFiles.add(file);
            });
        } else {
            FrontendReactTemplates.all().forEach(descriptor -> {
                GeneratedFile file = templateRenderService.render(descriptor, context);
                generatedFiles.add(file);
            });
        }
        if (scaffoldOptions.isEnableBlockchain()) {
            FiscoBaseTemplates.all().forEach(descriptor -> {
                GeneratedFile file = templateRenderService.render(descriptor, context);
                generatedFiles.add(file);
            });
            BlockchainTemplates.all().forEach(descriptor -> {
                GeneratedFile file = templateRenderService.render(descriptor, context);
                generatedFiles.add(file);
            });
            if (!scaffoldOptions.getChainFields().isEmpty()) {
                SolidityTemplates.all().forEach(descriptor -> {
                    GeneratedFile file = templateRenderService.render(descriptor, context);
                    generatedFiles.add(file);
                });
            }
        }
        return generatedFiles;
    }

    public List<GeneratedFileVO> generateMybatisPlusCrudView(ScaffoldGenerationRequest request) throws SqlParseException {
        return generateMybatisPlusCrud(request).stream()
                .map(file -> {
                    GeneratedFileVO vo = new GeneratedFileVO();
                    vo.setTemplateId(file.getTemplateId());
                    vo.setPath(file.getRelativePath());
                    vo.setContent(file.getContent());
                    return vo;
                })
                .collect(Collectors.toList());
    }

    private ScaffoldOptions enrichOptions(ClassInfo classInfo, ScaffoldOptions options) {
        ScaffoldOptions effective = options != null ? options : ScaffoldOptions.defaultOptions();
        if (classInfo.getFieldList() != null && !classInfo.getFieldList().isEmpty()) {
            if (!classInfo.getPrimaryKeyColumns().isEmpty()) {
                String firstPkColumn = classInfo.getPrimaryKeyColumns().get(0);
                classInfo.getFieldList().stream()
                        .filter(field -> firstPkColumn.equalsIgnoreCase(field.getColumnName()))
                        .findFirst()
                        .ifPresent(field -> effective.withPrimaryFieldIfAbsent(field.getFieldName()));
            }
            if (effective.getPrimaryFieldName() == null || effective.getPrimaryFieldName().isBlank()) {
                effective.withPrimaryFieldIfAbsent(classInfo.getFieldList().get(0).getFieldName());
            }
        }
        return effective;
    }
}
